package com.fll.server.config;

import com.fll.common.Config;
import com.fll.common.JsonUtil;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.charset.Charset;
import java.util.*;

/**
 * @author fanlei
 * @version 1.0
 * @date 2019年01月25日
 */
@Slf4j
public class ProxyConfig implements Serializable{
    private static final long serialVersionUID = 1L;
    /**
     * 类数据库
     */
    public static final String CONFIG_FILE;

    //netty server bind
    private String serverBind = Config.getInstance().getStringValue("server.bind", "0.0.0.0");
    //netty server port
    private Integer serverPort = Config.getInstance().getIntValue("server.port");

    //web bind
    private String configServerBind = Config.getInstance().getStringValue("config.server.bind", "0.0.0.0");
    //web port
    private Integer configServerPort = Config.getInstance().getIntValue("config.server.port");
    //web username
    private String configAdminUsername = Config.getInstance().getStringValue("config.admin.username");
    //web password
    private String configAdminPassword = Config.getInstance().getStringValue("config.admin.password");

    private List<ProxyConfig.Client> clients;
    private static ProxyConfig instance;
    private volatile Map<String, List<Integer>> clientInetPortMapping = new HashMap();
    private volatile Map<Integer, String> inetPortLanInfoMapping = new HashMap();
    private List<ProxyConfig.ConfigChangedListener> configChangedListeners = new ArrayList();

    private ProxyConfig() {
        log.info("config init serverBind {}, serverPort {}, configServerBind {}, configServerPort {}, configAdminUsername {}, configAdminPassword {}", new Object[]{this.serverBind, this.serverPort, this.configServerBind, this.configServerPort, this.configAdminUsername, this.configAdminPassword});
        this.update((String)null);
    }


    public void update(String proxyMappingConfigJson) {
        File file = new File(CONFIG_FILE);

        try {
            if (proxyMappingConfigJson == null && file.exists()) {
                InputStream in = new FileInputStream(file);
                byte[] buf = new byte[1024];
                ByteArrayOutputStream out = new ByteArrayOutputStream();

                int readIndex;
                while((readIndex = in.read(buf)) != -1) {
                    out.write(buf, 0, readIndex);
                }

                in.close();
                proxyMappingConfigJson = new String(out.toByteArray(), Charset.forName("UTF-8"));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        List<ProxyConfig.Client> clients = (List) JsonUtil.json2object(proxyMappingConfigJson, new TypeToken<List<Client>>() {
        });
        if (clients == null) {
            clients = new ArrayList();
        }

        Map<String, List<Integer>> clientInetPortMapping = new HashMap();
        Map<Integer, String> inetPortLanInfoMapping = new HashMap();
        Iterator it = ((List)clients).iterator();

        while(it.hasNext()) {
            ProxyConfig.Client client = (ProxyConfig.Client)it.next();
            String clientKey = client.getClientKey();
            if (clientInetPortMapping.containsKey(clientKey)) {
                throw new IllegalArgumentException("密钥同时作为客户端标识，不能重复： " + clientKey);
            }

            List<ProxyConfig.ClientProxyMapping> mappings = client.getProxyMappings();
            List<Integer> ports = new ArrayList();
            clientInetPortMapping.put(clientKey, ports);
            Iterator it1 = mappings.iterator();

            while(it1.hasNext()) {
                ProxyConfig.ClientProxyMapping mapping = (ProxyConfig.ClientProxyMapping)it1.next();
                Integer port = mapping.getInetPort();
                ports.add(port);
                if (inetPortLanInfoMapping.containsKey(port)) {
                    throw new IllegalArgumentException("一个公网端口只能映射一个后端信息，不能重复: " + port);
                }

                inetPortLanInfoMapping.put(port, mapping.getLan());
            }
        }

        this.clientInetPortMapping = clientInetPortMapping;
        this.inetPortLanInfoMapping = inetPortLanInfoMapping;
        this.clients = (List)clients;
        if (proxyMappingConfigJson != null) {
            try {
                FileOutputStream out = new FileOutputStream(file);
                out.write(proxyMappingConfigJson.getBytes(Charset.forName("UTF-8")));
                out.flush();
                out.close();
            } catch (Exception var14) {
                throw new RuntimeException(var14);
            }
        }

        this.notifyconfigChangedListeners();
    }

    private void notifyconfigChangedListeners() {
        List<ProxyConfig.ConfigChangedListener> changedListeners = new ArrayList(this.configChangedListeners);
        Iterator it = changedListeners.iterator();

        while(it.hasNext()) {
            ProxyConfig.ConfigChangedListener changedListener = (ProxyConfig.ConfigChangedListener)it.next();
            changedListener.onChanged();
        }

    }

    public void addConfigChangedListener(ProxyConfig.ConfigChangedListener configChangedListener) {
        this.configChangedListeners.add(configChangedListener);
    }

    public void removeConfigChangedListener(ProxyConfig.ConfigChangedListener configChangedListener) {
        this.configChangedListeners.remove(configChangedListener);
    }


    /**
     *
     * @return instance
     */
    public static ProxyConfig getInstance() {
        return instance;
    }


    static {
        String dataPath = System.getProperty("user.home") + "/" + ".flproxy/";
        File file = new File(dataPath);
        if (!file.isDirectory()) {
            file.mkdir();
        }

        CONFIG_FILE = dataPath + "/config.json";
        instance = new ProxyConfig();
    }



    public Integer getServerPort() {
        return this.serverPort;
    }

    public String getServerBind() {
        return this.serverBind;
    }

    public void setServerBind(String serverBind) {
        this.serverBind = serverBind;
    }

    public String getConfigServerBind() {
        return this.configServerBind;
    }

    public void setConfigServerBind(String configServerBind) {
        this.configServerBind = configServerBind;
    }

    public Integer getConfigServerPort() {
        return this.configServerPort;
    }

    public void setConfigServerPort(Integer configServerPort) {
        this.configServerPort = configServerPort;
    }

    public String getConfigAdminUsername() {
        return this.configAdminUsername;
    }

    public void setConfigAdminUsername(String configAdminUsername) {
        this.configAdminUsername = configAdminUsername;
    }

    public String getConfigAdminPassword() {
        return this.configAdminPassword;
    }

    public void setConfigAdminPassword(String configAdminPassword) {
        this.configAdminPassword = configAdminPassword;
    }

    public void setServerPort(Integer serverPort) {
        this.serverPort = serverPort;
    }

    public List<ProxyConfig.Client> getClients() {
        return this.clients;
    }

    public List<Integer> getClientInetPorts(String clientKey) {
        return (List)this.clientInetPortMapping.get(clientKey);
    }

    public Set<String> getClientKeySet() {
        return this.clientInetPortMapping.keySet();
    }

    public String getLanInfo(Integer port) {
        return (String)this.inetPortLanInfoMapping.get(port);
    }

    public List<Integer> getUserPorts() {
        List<Integer> ports = new ArrayList();
        Iterator ite = this.inetPortLanInfoMapping.keySet().iterator();

        while(ite.hasNext()) {
            ports.add((Integer) ite.next());
        }

        return ports;
    }








    /**
     * 配置改变接口
     */
    public interface ConfigChangedListener {
        void onChanged();
    }

    /**
     * 客户端映射类
     */
    public static class ClientProxyMapping {
        /**
         * 代理端口
         */
        private Integer inetPort;
        /**
         *
         */
        private String lan;
        /**
         * 别名
         */
        private String name;

        public ClientProxyMapping() {
        }

        public Integer getInetPort() {
            return this.inetPort;
        }

        public void setInetPort(Integer inetPort) {
            this.inetPort = inetPort;
        }

        public String getLan() {
            return this.lan;
        }

        public void setLan(String lan) {
            this.lan = lan;
        }

        public String getName() {
            return this.name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    /**
     * 客户端
     */
    public static class Client implements Serializable {
        private static final long serialVersionUID = 1L;
        private String name;
        private String clientKey;
        private List<ProxyConfig.ClientProxyMapping> proxyMappings;
        private int status;

        public Client() {
        }

        public String getClientKey() {
            return this.clientKey;
        }

        public void setClientKey(String clientKey) {
            this.clientKey = clientKey;
        }

        public List<ProxyConfig.ClientProxyMapping> getProxyMappings() {
            return this.proxyMappings;
        }

        public void setProxyMappings(List<ProxyConfig.ClientProxyMapping> proxyMappings) {
            this.proxyMappings = proxyMappings;
        }

        public String getName() {
            return this.name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getStatus() {
            return this.status;
        }

        public void setStatus(int status) {
            this.status = status;
        }
    }    
}
